package com.zhdl.network.modbustcp.analysis;

import com.project.common.util.ByteHelper;
import com.zhdl.network.modbustcp.dto.ModbusResp;

import java.io.IOException;

/**
 * ModBus协议解析类
 */
public class ModbusTcpAnalysis {
    /**
     * 完整数据包解析
     *
     * @param rcvBuff 218报头(12字节) + 数据单元（Length(2字节) + MFC + SFC + ... ）
     * @return 返回数据单元，包含Length+ MFC + SFC + ...
     * @throws IOException
     */
    private byte[] VerifyFullFrame(byte[] rcvBuff) throws Exception {
        // 确认数据包类型
        if (rcvBuff[0] != 0x19)
            throw new IOException("非MEMOBUS响应");

        // TODO 确认序列号
        // if (sendBuff[1] != rcvBuff[1]) // 与指令的序列号不一致
        //     throw new IOException("与指令的序列号不一致");

        // 获取数据包总长度 218报头起始至MEMOBUS数据末尾 （Length部(2字节) + MFC + SFC + ... ）
        byte[] bsTemp = new byte[2];
        System.arraycopy(rcvBuff, 6, bsTemp, 0, bsTemp.length);
        short fullLen = ByteHelper.byteArrayToShortLow(bsTemp);
        if (fullLen != rcvBuff.length)
            throw new IOException(String.format("数据包长度错误 %s <> %s ", fullLen, rcvBuff.length));

        // 应用数据长度
        bsTemp = new byte[2];
        System.arraycopy(rcvBuff, 12, bsTemp, 0, bsTemp.length);
        short unitLen = ByteHelper.byteArrayToShortLow(bsTemp);
        if (unitLen + 12 + 2 != rcvBuff.length)
            throw new IOException("数据包长度错误");

        byte[] unitBuff = new byte[unitLen];
        System.arraycopy(rcvBuff, 14, unitBuff, 0, unitBuff.length);

        return unitBuff;
    }

    /**
     * 0x09 读取保持寄存器的内容(扩展)
     *
     * @param rcvBuff 数据单元，包含Length+ MFC + SFC + ...
     */
    public byte[] ReadKeepRegisterExd(byte[] rcvBuff) throws Exception {
        byte[] unitBuff = this.VerifyFullFrame(rcvBuff);
        int beginIndex = 0;
        Byte bTemp;
        byte[] bsTemp;
        bTemp = unitBuff[beginIndex++];
        if (bTemp != 0x20)
            throw new IOException(String.format("mfc %s not 0x20", bTemp));

        bTemp = unitBuff[beginIndex++];
        if (bTemp != 0x09)
            throw new IOException(String.format("sfc %s not 0x09", bTemp));

        // CPU编号
        bTemp = unitBuff[beginIndex++];
        // 空 SPARE
        bTemp = unitBuff[beginIndex++];

        // 寄存器数据 2HEX
        bsTemp = new byte[2];
        System.arraycopy(unitBuff, beginIndex, bsTemp, 0, bsTemp.length);
        short count = ByteHelper.byteArrayToShortLow(bsTemp);
        beginIndex += 2;
        int registerCount = (unitBuff.length - beginIndex) / 2;  // 寄存器数量
        if (count != registerCount)
            throw new IOException(String.format("registerCount %s != count %s", registerCount, count));

        // 数据 N * 2HEX
        byte[] dataBuff = new byte[registerCount * 2];
        System.arraycopy(unitBuff, 6, dataBuff, 0, dataBuff.length);

        return dataBuff;
    }

    /**
     * 0x0E 保持寄存器的不连续写入(扩展)
     *
     * @param unitBuff 数据单元，包含Length+ MFC + SFC + ...
     * @return 返回写入寄存器数量
     */
    public ModbusResp<Short> WriteManyKeepRegisterExd(byte[] unitBuff) throws Exception {
        ModbusResp<Short> resp = new ModbusResp<>();
        // byte[] unitBuff = this.VerifyFullFrame(rcvBuff);
        int beginIndex = 0;
        // 应用数据长度 不包含当前项，当前项以后到结尾的数据项长度和
        byte[] bsTemp = new byte[2];
        bsTemp[0] = unitBuff[beginIndex++];
        bsTemp[1] = unitBuff[beginIndex++];
        short unitLen = ByteHelper.byteArrayToShortLow(bsTemp);
        if (unitLen + 2 != unitBuff.length)
            throw new IOException("数据包长度错误");

        if (unitBuff[beginIndex++] != 0x20)
            throw new IOException("mfc error");

        if (unitBuff[beginIndex++] != 0x0E)
            throw new IOException("sfc error");

        // 寄存器数量
        bsTemp = new byte[2];
        // Int本身存储低字节在前，传输协议也是低字节在前，所以一致
        bsTemp[0] = unitBuff[beginIndex++];
        bsTemp[1] = unitBuff[beginIndex++];
        short regCount = ByteHelper.byteArrayToShortLow(bsTemp);
        resp.setData(regCount);

        return resp;
    }

    /**
     * 读取寄存器的状态(32位地址)
     *
     * @param rcvBuff 数据单元，包含Length+ MFC + SFC + ...
     */
    public byte[] Read32bitRegisterStatus(byte[] rcvBuff) throws Exception {
        // unitBuff 43 49 01 00 47 00 02 00 0D 00 00 00
        byte[] unitBuff = this.VerifyFullFrame(rcvBuff);
        int beginIndex = 0;
        Byte bTemp;
        byte[] bsTemp;
        if (unitBuff[beginIndex++] != 0x43)
            throw new IOException("mfc error");

        if (unitBuff[beginIndex++] != 0x49)
            throw new IOException("sfc error");

        // CPU编号
        bTemp = unitBuff[beginIndex++];
        // 空
        bTemp = unitBuff[beginIndex++];
        // 寄存器种类 0x47
        bTemp = unitBuff[beginIndex++];
        // 空
        bTemp = unitBuff[beginIndex++];
        // 字数
        bsTemp = new byte[2];
        System.arraycopy(unitBuff, beginIndex, bsTemp, 0, bsTemp.length);
        short count = ByteHelper.byteArrayToShortLow(bsTemp);
        beginIndex += 2;
        int registerCount = (unitBuff.length - beginIndex) / 2;  // 寄存器数量
        if (count != registerCount)
            throw new IOException("registerCount error");

        // 数据单元，不包含长度
        byte[] dataBuff = new byte[registerCount * 2];
        System.arraycopy(unitBuff, beginIndex, dataBuff, 0, dataBuff.length);

        return dataBuff;
    }

    /**
     * 数据单元为两个寄存器，一个数值占2个字节，输出int列表
     *
     * @param buff 收到的数据单元
     * @return int列表
     */
    public int[] ChangeToL(byte[] buff) {
        int[] values = new int[buff.length / 4];
        byte[] bsTemp;
        for (int i = 0; i < values.length; i++) {
            int beginIndex = i * 4;
            bsTemp = new byte[4];
            // Int本身存储低字节在前，传输协议也是低字节在前，所以一致
            bsTemp[0] = buff[beginIndex];
            bsTemp[1] = buff[beginIndex + 1];
            bsTemp[2] = buff[beginIndex + 2];
            bsTemp[3] = buff[beginIndex + 3];
            values[i] = ByteHelper.byteArrayToInt(bsTemp);
        }
        return values;
    }

    /**
     * 数据单元为两个寄存器，一个数值占2个字节，输出short列表
     *
     * @param buff
     * @return
     */
    public short[] ChangeToW(byte[] buff) throws Exception {
        short[] values = new short[buff.length / 2];
        byte[] bsTemp;
        for (int i = 0; i < values.length; i++) {
            int beginIndex = i * 2;
            bsTemp = new byte[2];
            // Int本身存储低字节在前，传输协议也是低字节在前，所以一致
            bsTemp[0] = buff[beginIndex];
            bsTemp[1] = buff[beginIndex + 1];
            values[i] = ByteHelper.byteArrayToShortLow(bsTemp);
        }
        return values;
    }
}
